/**
 * ElectronExchanger Server Code File
 * 
 * File:        transaction.c
 * Brief:       BRIEF
 * Author:      Chen Kangbing
 * Email:       ckblau@outlook.com
 * Modified:    2020/09/09 09:28:56
 *
 */


#include "transaction.h"
#include "socket_helper.h"
#include "db_helper.h"
#include "md5.h"

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>


const char *_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

#define GET(key) protocol_get_value(recv, key)
#define GETS(key, size) protocol_get_value_size(recv, key, size)
#define SET(key, value) protocol_set_value(send, key, value)
#define SETS(key, value, size) protocol_set_value_size(send, key, value, size)

const char *SERVER_STR              = "ElectronExchanger_Server V0.0.1";
const char *CLIENT_STR              = "ElectronExchanger_Client V0.0.1";

#define HEADER() { \
    SET("SERVER", SERVER_STR); \
    char tmp[20]; \
    sprintf(tmp, "%d", current_time()); \
    SET("TIMESTAMP", tmp); \
}

const char *RET_OK                  = "100 OK";
const char *ERR_INVALID_COMMAND     = "200 Invalid Command";
const char *ERR_INVALID_SESSIONID   = "210 Invalid Session ID";
const char *ERR_EXPIRED_SESSIONID   = "211 Expired Session ID";
const char *ERR_INVALID_USERMAIL    = "220 Invalid Usermail";
const char *ERR_INVALID_PASSWORD    = "221 Invalid Password";
const char *ERR_INVALID_NICKNAME    = "222 Invalid Nickname";
const char *ERR_INVALID_MAILDATA    = "230 Invalid Mail Data";
const char *ERR_INVALID_MAILID      = "231 Invalid Mail ID";
const char *ERR_INVALID_RECIPIENT   = "232 Invalid Recipient";
const char *ERR_GENERAL_DB_ERROR    = "300 General DB Error";
const char *ERR_STORAGE_FS_ERROR    = "310 Storage FS Error";

#define RET(str) SET("RETVAL", str)
#define RETOK() RET(RET_OK)


int current_time() {
    time_t t;
    return time(&t);
}

void gen_session_id(char *psessid) {
    int i;
    for (i = 0; i < 32; ++i) {
        psessid[i] = _CHARS[rand() % 62];
    }
    psessid[32] = 0;
}

void gen_salt(char *psalt) {
    int i;
    for (i = 0; i < 16; ++i) {
        psalt[i] = _CHARS[rand() % 62];
    }
    psalt[16] = 0;
}

void md5_hash(char *raw, int size, char *hashstr) {
    unsigned char hash[16];    
    MD5_CTX md5;
    MD5Init(&md5);
    MD5Update(&md5, (unsigned char *)raw, size);
    MD5Final(&md5, hash);
    char *buf = hashstr;
    int i;
    for (i = 0; i < 16; ++i) 
        buf += sprintf(buf, "%02x", hash[i]);
}

void make_password(char *password_sha1, char *salt, char *password_md5) {
    int size = strlen(password_sha1);
    char password_salty[80];
    memcpy(password_salty, password_sha1, size);
    memcpy(password_salty + size, salt, 16);
    md5_hash(password_salty, size + 16, password_md5);
}

int parse_mail(char *mailstr, int mailsize, DB_EmailInfoTypeDef *mail, int *read_state, char *content) {
    int line_num = 1;
    char *line[15];
    char *cur = mailstr;
    char *eom = mailstr + mailsize;
    line[0] = cur;
    while (cur < eom && line_num <= 9) {
        if (*cur == '\n') {
            *cur = 0;
            line[line_num] = cur + 1;
            ++line_num;
        }
        ++cur;
    }
    if (line_num < 9) {
        return 1;
    }
    
    *read_state = atol(line[0]);
    strcpy(mail->FromEmail, line[1]);
    strcpy(mail->ToEmails, line[2]);
    mail->BCCEmails[0] = 0;
    mail->Time = atol(line[3]);
    mail->FileID = atol(line[4]);
    // line[5] line[6] not used;
    strcpy(mail->Subject, line[7]);
    if (content != NULL) strcpy(content, line[8]);

    return 0;
}

int build_mail(DB_EmailInfoTypeDef *mail, int read_state, char *filename, int filesize, char *content, char *mailstr) {
    char *buf = mailstr;
    buf += sprintf(buf, "%d\n", read_state);
    buf += sprintf(buf, "%s\n", mail->FromEmail);
    buf += sprintf(buf, "%s\n", mail->ToEmails);
    buf += sprintf(buf, "%d\n", mail->Time);
    buf += sprintf(buf, "%d\n", mail->FileID);
    if (mail->FileID > 0) {
        buf += sprintf(buf, "%s\n", filename);
        buf += sprintf(buf, "%d\n", filesize);
    }
    else {
        buf += sprintf(buf, "%s\n", "NULL");
        buf += sprintf(buf, "%s\n", "NULL");
    }
    buf += sprintf(buf, "%s\n", mail->Subject);
    buf += sprintf(buf, "%s\n", content);
    return buf - mailstr;
}

int save_file(DB_FileDataTypeDef *file, char *content) {
    sprintf(file->FilePath, "./storage/%d.file", file->FileID);
    FILE *fp = fopen(file->FilePath, "w");
    if (fp == NULL) return 1;
    fwrite(content, file->FileSize, 1, fp);
    fclose(fp);
    return 0;
}

int load_file(DB_FileDataTypeDef *file, char *content) {
    FILE *fp = fopen(file->FilePath, "r");
    if (fp == NULL) return 1;
    fread(content, file->FileSize, 1, fp);
    fclose(fp);
    return 0;
}

// ----------------------------------------------

int cb_register(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    DB_UserDataTypeDef userdata;

    int last_uid;
    ret = mysql_get_serverdata("LastUID", &last_uid);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
    usr->user_id = last_uid + 1;
    userdata.UserID = usr->user_id;

    char *usermail = GET("USERMAIL");
    strcpy(userdata.UserEmail, usermail);

    char *nickname = GET("NICKNAME");
    strcpy(userdata.NickName, nickname);

    gen_salt(userdata.Salt);

    char *password_sha1 = GET("PASSWORD");
    make_password(password_sha1, userdata.Salt, userdata.Password);

    userdata.RegistrationTime = current_time();

    ret = mysql_register(&userdata);
    if (ret == 2) return RET(ERR_INVALID_USERMAIL);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    ret = mysql_set_serverdata("LastUID", usr->user_id);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    return RETOK();
}

int cb_login(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;

    char *email = GET("USERMAIL");
    ret = mysql_get_target_userid(email, &usr->user_id);
    if (ret == 2) return RET(ERR_INVALID_USERMAIL);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    DB_UserDataTypeDef userdata;
    ret = mysql_get_userinfo(usr->user_id, &userdata);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    char *password_sha1 = GET("PASSWORD");
    char password_md5[40];
    make_password(password_sha1, userdata.Salt, password_md5);
    if (strcmp(password_md5, userdata.Password) != 0)
        return RET(ERR_INVALID_PASSWORD);
    
    usr->expire_time = current_time() + 300;
    gen_session_id(usr->session_id);
    ret = mysql_create_session_id(usr->session_id, usr->user_id, usr->expire_time);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
    SET("SESSIONID", usr->session_id);

    char tmp[20];
    sprintf(tmp, "%d", usr->expire_time);
    SET("EXPIRE", tmp);

    return RETOK();
}

int cb_logout(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    ret = mysql_destroy_session_id(usr->session_id);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    GET("CLIENT");

    return RETOK();
}

int cb_set_password(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    DB_UserDataTypeDef userdata;
    ret = mysql_get_userinfo(usr->user_id, &userdata);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    char *password_sha1 = GET("OLDPASSWORD");
    char password_md5[40];
    make_password(password_sha1, userdata.Salt, password_md5);
    if (strcmp(password_md5, userdata.Password) != 0)
        return RET(ERR_INVALID_PASSWORD);

    gen_salt(userdata.Salt);
    password_sha1 = GET("NEWPASSWORD");
    make_password(password_sha1, userdata.Salt, userdata.Password);

    ret = mysql_set_password(usr->user_id, userdata.Password, userdata.Salt);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    return RETOK();
}

int cb_get_userinfo(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    DB_UserDataTypeDef userdata;
    ret = mysql_get_userinfo(usr->user_id, &userdata);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    RETOK();
    SET("USERMAIL", userdata.UserEmail);
    SET("NICKNAME", userdata.NickName);

    char tmp[20];
    sprintf(tmp, "%d", userdata.RegistrationTime);
    SET("REGTIME", tmp);

    GET("CLIENT");

    return 0;
}

int cb_set_userinfo(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    ret = mysql_set_nickname(usr->user_id, GET("NICKNAME"));
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    GET("CLIENT");

    return RETOK();
}

int cb_get_contact_list(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    int size;
    DB_ContactDataTypeDef contacts[200];
    ret = mysql_get_contact_list(usr->user_id, contacts, &size);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    char contact_list[10000];
    char *buf = contact_list;
    buf[0] = 0;
    int i;
    for (i = 0; i < size; ++i) {
        DB_UserDataTypeDef contact_user;
        ret = mysql_get_userinfo(contacts[i].ContactID, &contact_user);
        if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
        buf += sprintf(buf, "%s,%s\n", contact_user.UserEmail, contacts[i].ContactNickName);
    }

    RETOK();
    SET("#CONTACTLIST", contact_list);

    GET("CLIENT");

    return 0;
}

int cb_add_contact(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    int target_uid;
    ret = mysql_get_target_userid(GET("CONTACTMAIL"), &target_uid);
    if (ret == 2) return RET(ERR_INVALID_USERMAIL);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    DB_ContactDataTypeDef contact;
    contact.UserID = usr->user_id;
    contact.ContactID = target_uid;
    strcpy(contact.ContactNickName, GET("NICKNAME"));
    ret = mysql_add_contact(&contact);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    return RETOK();
}

int cb_delete_contact(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    int target_uid;
    ret = mysql_get_target_userid(GET("CONTACTMAIL"), &target_uid);
    if (ret == 2) return RET(ERR_INVALID_USERMAIL);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    ret = mysql_delete_contact(usr->user_id, target_uid);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    return RETOK();
}

int cb_set_contact_nickname(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    int target_uid;
    ret = mysql_get_target_userid(GET("CONTACTMAIL"), &target_uid);
    if (ret == 2) return RET(ERR_INVALID_USERMAIL);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    ret = mysql_set_contact_nickname(usr->user_id, target_uid, GET("NICKNAME"));
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    return RETOK();
}

int cb_get_target_nickname(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    int target_uid;
    ret = mysql_get_target_userid(GET("CONTACTMAIL"), &target_uid);
    if (ret == 2) return RET(ERR_INVALID_USERMAIL);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    char target_nickname[100];
    ret = mysql_get_target_nickname(target_uid, target_nickname);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
    SET("NICKNAME", target_nickname);

    return RETOK();
}

int cb_get_mail_list(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    DB_EmailListTypeDef *mails = malloc(1000 * sizeof(DB_EmailListTypeDef));
    int size;
    ret = mysql_get_mail_list(usr->user_id, GET("BOX"), mails, &size);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    char *mail_list = malloc(10000 * sizeof(char));
    char *buf = mail_list;
    buf[0] = 0;
    int i;
    for (i = 0; i < size; ++i) {
        DB_EmailInfoTypeDef mail;
        ret = mysql_get_mail(mails[i].EmailNo, &mail, NULL);
        if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
        buf += sprintf(buf, "%d,%s,%d,%d,%s\n", mails[i].EmailNo, mail.FromEmail, mails[i].Time, mails[i].ReadState, mail.Subject);
    }

    RETOK();
    SET("#MAILLIST", mail_list);

    free(mails);
    free(mail_list);
    return 0;
}

int cb_get_mail(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    int exist;
    DB_EmailInfoTypeDef mail;
    mail.EmailNo = atol(GET("MAILID"));
    ret = mysql_check_mail_exist(usr->user_id, mail.EmailNo, "Inbox", &exist);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
    if (exist) {
        ret = mysql_set_read_state(usr->user_id, mail.EmailNo, 1);
        if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
    }
    
    char *content = malloc(200000);
    ret = mysql_get_mail(mail.EmailNo, &mail, content);
    if (ret == 2) return RET(ERR_INVALID_MAILID);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    DB_FileDataTypeDef file;
    if (mail.FileID > 0) {
        ret = mysql_get_file(mail.FileID, &file);
        if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
    }
    else {
        file.FileName[0] = 0;
        file.FileSize = -1;
    }

    RETOK();

    char *mailstr = malloc(200000);
    int size = build_mail(&mail, 0, file.FileName, file.FileSize, content, mailstr);
    SETS("#MAIL", mailstr, size);

    free(content);
    free(mailstr);
    return 0;
}

int cb_upload_mail(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    int last_eno;
    DB_EmailInfoTypeDef mail;
    ret = mysql_get_serverdata("LastEno", &last_eno);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
    mail.EmailNo = last_eno + 1;

    int read_state;
    int size = -1;
    char *content = malloc(200000);
    char *mail_str = GETS("#MAIL", &size);
    ret = parse_mail(mail_str, size, &mail, &read_state, content);
    if (ret != 0) return RET(ERR_INVALID_MAILDATA);

    ret = mysql_upload_mail(&mail, content);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    ret = mysql_set_serverdata("LastEno", mail.EmailNo);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    ret = mysql_save_mail(mail.EmailNo, usr->user_id, "Drafts");
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    RETOK();
    
    char tmp[20];
    sprintf(tmp, "%d", mail.EmailNo);
    SET("MAILID", tmp);

    free(content);
    return 0;    
}

int cb_send_mail(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    int eno = atol(GET("MAILID"));
    DB_EmailInfoTypeDef mail;
    ret = mysql_get_mail(eno, &mail, NULL);
    if (ret == 2) return RET(ERR_INVALID_MAILID);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    char *cur = mail.ToEmails;
    char *to_mail = cur;
    char *eol = cur + strlen(mail.ToEmails);
    while (cur <= eol) {
        if (*cur == ',' || cur == eol) {
            *cur = 0;

            int to_uid;
            ret = mysql_get_target_userid(to_mail, &to_uid);
            if (ret == 2) return RET(ERR_INVALID_RECIPIENT);
            if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

            ret = mysql_save_mail(eno, to_uid, "Inbox");
            if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

            to_mail = cur + 1;
        }
        ++cur;
    }

    ret = mysql_delete_mail(eno, usr->user_id, "Drafts");
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
    ret = mysql_save_mail(eno, usr->user_id, "Sent");
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    return RETOK();
}

int cb_delete_mail(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    // char BOX[4][10] = {"Inbox", "Sent", "Drafts", "Trash"};
    // int boxid = -1;

    // int exist;
    int eno = atol(GET("MAILID"));
    // int i;
    // for (i = 0; i < 4; ++i) {
    //     ret = mysql_check_mail_exist(usr->user_id, eno, BOX[i], &exist);
    //     if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
    //     if (exist) boxid = i;
    // }
    // if (boxid < 0) return RET(ERR_INVALID_MAILID);
    
    // ret = mysql_delete_mail(eno, usr->user_id, BOX[boxid]);
    ret = mysql_delete_mail(eno, usr->user_id, GET("BOX"));
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
    
    int to_trash = atol(GET("TOTRASH"));
    if (to_trash) {
        ret = mysql_save_mail(eno, usr->user_id, "Trash");
        if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);
    }

    return RETOK();
}

int cb_upload_file(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    int last_fid;
    DB_FileDataTypeDef file;
    ret = mysql_get_serverdata("LastFID", &last_fid);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    file.FileID = last_fid + 1;
    file.FileSize = atol(GET("FILESIZE"));
    strcpy(file.FileName, GET("FILENAME"));
    file.FileTime = atol(GET("TIMESTAMP"));

    char *content = GET("#FILE");
    ret = save_file(&file, content);
    if (ret != 0) return RET(ERR_STORAGE_FS_ERROR);

    ret = mysql_upload_file(&file);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    ret = mysql_set_serverdata("LastFID", file.FileID);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    RETOK();
    
    char tmp[20];
    sprintf(tmp, "%d", file.FileID);
    SET("FILEID", tmp);

    return 0;
}

int cb_download_file(SessionDataTypeDef *usr, ProtoPackTypeDef *recv, ProtoPackTypeDef *send) {
    int ret;
    DB_FileDataTypeDef file;
    file.FileID = atol(GET("FILEID"));
    ret = mysql_get_file(file.FileID, &file);
    if (ret != 0) return RET(ERR_GENERAL_DB_ERROR);

    char *file_data = malloc(file.FileSize);
    ret = load_file(&file, file_data);
    if (ret != 0) return RET(ERR_STORAGE_FS_ERROR);

    RETOK();
    SET("FILENAME", file.FileName);

    char tmp[20];
    sprintf(tmp, "%d", file.FileSize);
    SET("FILESIZE", tmp);
    SETS("#FILE", file_data, file.FileSize);

    free(file_data);
    return 0;
}

#define MAX_COMMAND_NUM 18
const CmdCallbackDriverTypeDef CALLBACK_TABLE[MAX_COMMAND_NUM] = {
    {"REGISTER",            &cb_register,               0}, 
    {"LOGIN",               &cb_login,                  0}, 
    {"LOGOUT",              &cb_logout,                 1}, 
    {"SET-PASSWORD",        &cb_set_password,           1 | 2},
    {"GET-USERINFO",        &cb_get_userinfo,           1 | 2},
    {"SET-USERINFO",        &cb_set_userinfo,           1 | 2},
    {"GET-CONTACTLIST",     &cb_get_contact_list,       1 | 2},
    {"ADD-CONTACT",         &cb_add_contact,            1 | 2},
    {"DELETE-CONTACT",      &cb_delete_contact,         1 | 2},
    {"SET-CONTACTNICKNAME", &cb_set_contact_nickname,   1 | 2},
    {"GET-TARGETNICKNAME",  &cb_get_target_nickname,    1 | 2},
    {"GET-MAILLIST",        &cb_get_mail_list,          1 | 2},
    {"GET-MAIL",            &cb_get_mail,               1 | 2},
    {"UPLOAD-MAIL",         &cb_upload_mail,            1 | 2},
    {"SEND-MAIL",           &cb_send_mail,              1 | 2},
    {"DELETE-MAIL",         &cb_delete_mail,            1 | 2},
    {"UPLOAD-FILE",         &cb_upload_file,            1 | 2},
    {"DOWNLOAD-FILE",       &cb_download_file,          1 | 2}
};

// ----------------------------------------------


int transaction_worker(TransmissionTypeDef *ptrans) {

    // const char *MYSTR = "Hello there!\n";
    // printf("Recv from client: \"%s\"", ptrans->down_data);
    // ptrans->up_data_size = strlen(MYSTR);
    // ptrans->up_data = malloc(ptrans->up_data_size + 1);
    // strcpy(ptrans->up_data, MYSTR);

    ProtoPackTypeDef pack_recv;
    ProtoPackTypeDef *recv = &pack_recv;
    int rv;
    rv = protocol_decode(ptrans, recv);
    if (rv != 0) return rv;

    ptrans->up_data = malloc(1000);
    ptrans->up_data_size = 0;
    for (int i = 0; i < recv->pair_size; ++i) {
        printf("%s: len(%d) \"%s\"\n", recv->key[i], recv->value_size[i], (recv->value_size[i] < 1000) ? recv->value[i] : "NOT SHOWN");
    }
    // for (int i = 0; i < recv.pair_size; ++i) {
    //     ptrans->up_data_size += sprintf(ptrans->up_data + ptrans->up_data_size, "%s: len(%d) \"%s\"\n", recv.key[i], recv.value_size[i], recv.value[i]);
    // }

    ProtoPackTypeDef pack_send;
    ProtoPackTypeDef *send = &pack_send;
    protocol_build_pack(send);

    // SET("SERVER", "ElectronExchanger_Client V0.0.1");
    // SET("TIMESTAMP", "12345678");
    // SET("SESSIONID", "abcdefghijk");
    // SET("COMMAND", "hello");
    // SET("KEY1", "value1");

    SessionDataTypeDef sess_usr;
    SessionDataTypeDef *usr = &sess_usr;

    usr->timestamp = atol(GET("TIMESTAMP"));
    if (abs(current_time() - usr->timestamp) > 30000000) {
        return 1;           // invalid time
    }
        

    int i, flag = 0;
    for (i = 0; i < MAX_COMMAND_NUM; ++i) {
        if (strcmp(CALLBACK_TABLE[i].cmd, GET("COMMAND")) == 0) {
            flag = 1;
            HEADER();
            if (CALLBACK_TABLE[i].type & 1) {
                strcpy(usr->session_id, GET("SESSIONID"));
                mysql_check_session_id(usr->session_id, &usr->user_id, &usr->expire_time);
                if (usr->user_id < 0) {
                    RET(ERR_INVALID_SESSIONID);
                    break;
                }
                if (usr->expire_time + 3000000 < current_time()) {
                    RET(ERR_EXPIRED_SESSIONID);
                    break;
                }
            }
            if (CALLBACK_TABLE[i].type & 2) {
                usr->expire_time = current_time() + 300;
                mysql_refresh_session_id(usr->session_id, usr->expire_time);
                char tmp[20];
                sprintf(tmp, "%d", usr->expire_time);
                SET("EXPIRE", tmp);
            }
            rv = (*CALLBACK_TABLE[i].callback)(usr, recv, send);
            if (rv != 0) {

            }
            if (CALLBACK_TABLE[i].type & 1) {

            }
            if (CALLBACK_TABLE[i].type & 2) {

            }
            break;
        }
    }
    if (!flag) {
        RET(ERR_INVALID_COMMAND);
    }

    protocol_encode(send, ptrans);

    for (int i = 0; i < send->pair_size; ++i) {
        printf("%s: len(%d) \"%s\"\n", send->key[i], send->value_size[i], (send->value_size[i] < 1000) ? send->value[i] : "NOT SHOWN");
    }

    return 0;
}

void *transaction_main(void *parg) {
    TransmissionTypeDef *ptrans;
    int rv;
    if (!parg) {
        printf("Invalid arguments from %s()\n", __FUNCTION__);
        pthread_exit(NULL);
    }
    ptrans = (TransmissionTypeDef *)parg;
    printf("thread [%ld]: ", ptrans->thread_tid);
    printf("Starting transaction ...\n");

    rv = transmit_down(ptrans);
    if (rv != 0) {
        shutdown_transmission(ptrans);
        destroy_transmission(ptrans);
        printf("thread [%ld]: ", ptrans->thread_tid);
        printf("Transaction failed in \"transmit_down\" with return value %d.\n", rv);
        pthread_exit(NULL);
    }

    rv = transaction_worker(ptrans);
    if (rv != 0) {
        shutdown_transmission(ptrans);
        destroy_transmission(ptrans);
        printf("thread [%ld]: ", ptrans->thread_tid);
        printf("Transaction failed in \"transaction_worker\" with return value %d.\n", rv);
        pthread_exit(NULL);
    }

    rv = transmit_up(ptrans);
    if (rv != 0) {
        shutdown_transmission(ptrans);
        destroy_transmission(ptrans);
        printf("thread [%ld]: ", ptrans->thread_tid);
        printf("Transaction failed in \"transmit_up\" with return value %d.\n", rv);
        pthread_exit(NULL);
    }

    shutdown_transmission(ptrans);
    destroy_transmission(ptrans);
    printf("thread [%ld]: ", ptrans->thread_tid);
    printf("Transaction finished.\n");
    pthread_exit(NULL);
}

